
/**
 * @file	logout.c
 * @brief	日志文件函数.
 * @author	Alan
 * @date	2008-8-21 13:13:17
 * @note	可以写入文件,也可以到控制台. 文件达到上限时,会生成新的日志文件.
 * @see		logout.h
**/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <stdarg.h>
#include <time.h>
#include <unistd.h>
#include <signal.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>


#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <fcntl.h>

#include <zc_log.h>

struct zc_log_tt
{
    int flag;                       /// 日志输出标记.
    char label[128];                /// 日志文件名前的标记.
    int level;                      /// 日志级别 0~9. 默认为0.
    
    char ip[15];                    /// 日志服务器的IP.
    short port;                     /// 日志服务器的端口.
    struct sockaddr_in sin;         ///
    int fd;                         /// 创建的UDP端口.
        
    FILE *fp;                       /// 全局日志文件句柄.
    
	int log_idx;                    /// 当前日志数量.
    int line_cnt;                   /// 文件中最大行数.
    char log_buf[LOG_LINE_CNT][LOG_LINE_SIZE];   /// 日志缓存.    
};

////////////////////////////////////////

/**
 * @brief  初始化日志.
 * @param  label 构成日志文件的串.
 * @param  level 日志级别,0~9,数字越小,级别越高.
 * @param  flag  日志文件输出位置标记. 参见头文件宏定义.
 * @return 返回 0.
 * @see	   flag 的值参见头文件宏定义.
**/
int zc_log_init(zc_log_t *log, const char *label, int level, int flag, int fd, char *ip, short port)
{
    bzero(log, sizeof(zc_log_t));

    log->flag = flag;
    log->level = level;
    strncpy(log->label, label, sizeof(log->label)-1);
        
    log->fp = NULL;
    
    log->log_idx = 0;
    log->line_cnt = 0;

    log->fd = fd;
    strncpy(log->ip, ip, sizeof(log->ip)-1);
    log->port = port;    

    return 0;
}

int zc_log_udp(zc_log_t *log)
{
    if(log->fd < 0) {
        if(log->ip == NULL || log->port <= 0) {
            return -1;
        }
        
    	struct sockaddr_in sin;
    	int sockfd;
    
    	memset (&sin, '\0', sizeof (sin));
    
        log->sin.sin_family = AF_INET;	
    	log->sin.sin_addr.s_addr = inet_addr(log->ip);
        log->sin.sin_port = htons(log->port);
    
        
        sockfd = socket (PF_INET, SOCK_DGRAM, 0);
        if (sockfd == -1)
        {
    		perror ("socket() error");
    		return -1;
        }
        
        int flag;
        flag = fcntl(sockfd,F_GETFL);
       	fcntl(sockfd, F_SETFL, flag|O_NONBLOCK);
       	
       	log->fd = sockfd;
    }    

    return 0;    
}

int zc_log_file(zc_log_t *log)
{
    char filename[256];         // 文件名.
    struct tm *timeval;         // 格式化时间,构成文件名.
    time_t timet;               // 格式化时间,构成文件名.
    size_t len;                 // 格式化后的时间串长度.
    char strtime[32];           // 格式化后的时间串.
        
    if (log->fp != NULL)
    {
        zc_log_flush(log);
        fclose(log->fp);
    }
    
    /// 构造文件名: label_日期_时间.log
    time(&timet);
    timeval = localtime(&timet);
    bzero(strtime, sizeof(strtime));
    len = strftime(strtime, sizeof(strtime) - 1, "%F_%T", timeval);
    bzero(filename, sizeof(filename));
    snprintf(filename, sizeof(filename) - 1, "%s_%s.log", log->label, strtime);
    
    log->fp = fopen(filename, "wp");
    if (log->fp == NULL)
    {
        log->flag = LOG_NONE;
    }    
    
    return 0;
}


/**
 * @brief  打开日志功能.
 * @param  label 构成日志文件的串.
 * @param  level 日志级别,0~9,数字越小,级别越高.
 * @param  flag  日志文件输出位置标记. 参见头文件宏定义.
 * @return 返回 0.
 * @see	   flag 的值参见头文件宏定义.
**/
int zc_log_open(zc_log_t *log)
{
    switch (log->flag)
    {
    case LOG_FILE:
        zc_log_file(log);
        break;
    case LOG_UDP:
        zc_log_udp(log);
        break;
    case LOG_STDOUT:
        log->fp = stderr;
        break;
    case LOG_NONE:
    default:
        log->fp = NULL;
        log->fd = -1;
        log->flag = LOG_NONE;
        break;
    }

    return 0;
}

/**
 * @brief  输出并清除日志缓存.
 * @return 无
**/
static void zc_log_flush(zc_log_t *log)
{
    int i;                      // 用于for循环的临时变量.

    if (log->flag == LOG_NONE)
    {
        return;
    }
    /// 输出日志.重置日志数量.
    for (i = 0; i < log->log_idx; i++)
    {
        fprintf(log->fp, log->log_buf[i]);
    }
    log->log_idx = 0;

    /// 清理缓存.
    fflush(log->fp);

    return;
}

/**
 * @brief  关闭日志文件.
 * @return 返回 0.
**/
int zc_log_close(zc_log_t *log)
{
    /// 刷新日志缓存.
    zc_log_flush(log);

    /// 关闭文件.
    if (log->flag == LOG_FILE && log->fp != NULL)
    {
        fflush(log->fp);
        fclose(log->fp);
        log->log_idx = 0;
        log->fp = NULL;
        close(log->fd);
    }

    return 0;
}

/**
 * @brief  日志输出函数.
 * @param  file 	文件名.
 * @param  func		函数名.
 * @param  line		行号.
 * @param  level 	日志级别,0~9,数字越小,级别越高.
 * @param  fmt		格式, 参见 printf().
 * @param  ...		变长参数
 * @return 0:正常返回; -1:级别错误; -2:数据超长.
 * @note   支持变长参数,为了方便,调用LOGOUT宏,代替这个函数.\n
 		   每条记录的最大长度为	LOG_LINE_SIZE-1, 大于这个数字的记录被忽略.
 * @see	   格式参数的详细信息参见 printf().
**/
int zc_log(const char *file, const char *func, const int line,
           zc_log_t *log, const int level, const char *fmt, ...)
{
    va_list vl;                 // 用于变长参数.
    struct tm *timeval;         // 用来格式化时间.
    time_t timet;               // 用来格式化时间.
    size_t len;                 // 字符串长度.
    char buf[LOG_LINE_SIZE];     // 临时存放字符串.

    if (log->flag == LOG_NONE || level > log->level)
    {
        return 0;
    }
    /// 判断日志的级别,是否打印.
    if (level < 0 || level > 9)
    {
        return -1;
    }

    bzero(log->log_buf[log->log_idx], LOG_LINE_SIZE);

    /// 加入构造的时间串.
    bzero(buf, sizeof(buf));
    time(&timet);
    timeval = localtime(&timet);
    len = strftime(buf, sizeof(buf) - 1, "[%F %T] ", timeval);
    if (len > LOG_LINE_SIZE)
    {
        return -2;
    }
    strncat(log->log_buf[log->log_idx], buf, len);

    /// 加入打印日志的位置.
    bzero(buf, sizeof(buf));
    len = snprintf(buf, sizeof(buf) - 1, "%s : %s() : %d :: [%d] ", file, func, line, level);
    if (len > LOG_LINE_SIZE - strlen(log->log_buf[log->log_idx]))
    {
        return -2;
    }
    strncat(log->log_buf[log->log_idx], buf, len);

    /// 加入日志内容.
    bzero(buf, sizeof(buf));
    va_start(vl, fmt);
    len = vsnprintf(buf, sizeof(buf) - 1, fmt, vl);
    va_end(vl);
    if (len > LOG_LINE_SIZE - strlen(log->log_buf[log->log_idx]))
    {
        return -2;
    }
    strncat(log->log_buf[log->log_idx], buf, strlen(buf));

    /// 日志输出,如果是控制台则直接输出,如果是文件则入缓存.
    if (log->flag == LOG_STDOUT && log->fp != NULL)
    {
        fprintf(log->fp, log->log_buf[log->log_idx]);
    }
    else if (log->flag == LOG_FILE && log->fp != NULL)
    {
        log->log_idx++;

        /// 缓存满则输出缓存内容.
        if (log->log_idx >= LOG_LINE_CNT)
        {
            log->line_cnt += log->log_idx;

            zc_log_flush(log);

            /// 打开一个新的日志文件.
            if (log->line_cnt >= LOG_FILE_SIZE)
            {
                fclose(log->fp);
                log->fp = NULL;
                zc_log_file(log);
            }
        }
    } else if(log->flag == LOG_UDP && log->fd > 0) {
        sendto(log->fd, log->log_buf[log->log_idx], strlen(log->log_buf[log->log_idx]), 0, (struct sockaddr *)&log->sin, sizeof(log->sin));       
    }

    return 0;
}

